package com.yellow.cloud.security.aspect;

import com.alibaba.fastjson.JSON;
import com.yellow.cloud.security.oauth2.client.SystemClient;
import com.yellow.cloud.security.oauth2.service.AuthService;
import com.yellowframework.cloud.common.constant.Constants;
import com.yellowframework.cloud.common.exception.ExceptionCast;
import com.yellowframework.cloud.domain.security.request.LoginRequest;
import com.yellowframework.cloud.domain.security.response.AuthCode;
import com.yellowframework.cloud.domain.system.LoginLog;
import com.yellowframework.cloud.domain.system.SysDetailLog;
import com.yellowframework.cloud.domain.system.code.SysDetailLogCode;
import com.yellowframework.cloud.utils.Oauth2Utils;
import com.yellowframework.cloud.utils.RedisUtils;
import com.yellowframework.cloud.utils.SystemUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.CompletableFuture;

/**
 * 认证授权切面
 * 		可以对：登录失败次数过多进行管制
 * 		可以对：登录、登出日志进行重点记录
 * 		可以对：用户的最后一次登录时间进行记录
 * 		...
 * @author zhouhao
 *
 */
@Aspect
@Component
public class AuthAspect {

	@Resource
	private SystemClient systemClient;

	@Resource
	private RedisUtils redisUtils;

	@Resource
	private HttpServletRequest request;

	@Pointcut("execution( * com.yellow.cloud.security.oauth2.controller.AuthController.login(..))")
	public void login() {}

	@Pointcut("execution( * com.yellow.cloud.security.oauth2.controller.AuthController.logout(..))")
	public void logout() {}

	@Before("login()")
	public void loginCheck() {
		// 风控机制校验
		if (checkLoginError(SystemUtils.getIp(request))) {
			ExceptionCast.cast(AuthCode.AUTH_LOGIN_ERROR_CONTROL);
		}
	}

	/**
	 * 记录登录日志
	 * @author Hao.
	 * @date 2022/6/27 9:16
	 * @return void
	 */
	@AfterReturning("login()")
	public void loginSuccess() {
		final String username = AuthService.USERNAME.get();
		AuthService.USERNAME.remove();

		// 登录成功，删除登录限制
		deleteLoginErrorTimes(SystemUtils.getIp(request));

		// 获取当前线程请求头信息(解决异步调用丢失请求头问题)
		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

		final CompletableFuture<Void> cf1 = CompletableFuture.runAsync(() -> {
			RequestContextHolder.setRequestAttributes(requestAttributes);
			systemClient.setLastLoginTime(username);
		});

		final CompletableFuture<Void> cf2 = CompletableFuture.runAsync(() -> {
			RequestContextHolder.setRequestAttributes(requestAttributes);
			systemClient.saveLoginlog(LoginLog.builder()
					.username(username).status("登录" + Constants.OPERATE_STATUS_SUCCESS).build());
		});
	}

	/**
	 * 记录登录异常日志
	 * @param point
	 * @param e
	 * @return void
	 * @author zhouhao
	 * @date  2021/4/23 10:22
	 */
	@AfterThrowing(pointcut = "login()", throwing = "e")
	public void logonException(JoinPoint point, Throwable e) {
		AuthService.USERNAME.remove();

		// 添加登录失败次数
		addLoginErrorTimes(SystemUtils.getIp(request));


		// 获取当前线程请求头信息(解决异步调用丢失请求头问题)
		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

		LoginLog log = LoginLog.builder()
				.username(JSON.parseObject(JSON.toJSONString(point.getArgs()[0]), LoginRequest.class).getUsername())
				.status("登录" + Constants.OPERATE_STATUS_FAIL).build();

		CompletableFuture.supplyAsync(() -> {
			//每一个线程都来共享之前的请求数据
			RequestContextHolder.setRequestAttributes(requestAttributes);
			return systemClient.saveLoginlog(log);
		}).thenAcceptAsync(result -> {
			RequestContextHolder.setRequestAttributes(requestAttributes);
			systemClient.saveDetaillog(SysDetailLog.builder().logId(result.getId())
					.logType(Integer.parseInt(SysDetailLogCode.LOG_TYPE_1.k()))
					.detail(SystemUtils.getStackTrace(e)).build());
		});
	}

	/**
	 * 记录登出日志
	 * @author Hao.
	 * @date 2022/6/27 9:16
	 * @return void
	 */
	@Before("logout()")
	public void logoutLog() {
		// 获取当前线程请求头信息(解决异步调用丢失请求头问题)
		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
		CompletableFuture.runAsync(() -> {
			RequestContextHolder.setRequestAttributes(requestAttributes);
			systemClient.saveLoginlog(LoginLog.builder()
					.username(Oauth2Utils.getCurrentUsername()).status("登出" + Constants.OPERATE_STATUS_SUCCESS).build());
		});
	}

	/**
	 * 缓存该IP地址用户登录错误次数
	 *
	 * @param ip
	 */
	public void addLoginErrorTimes(String ip) {
		String key = Constants.LOGIN_FAIL_IP + ip;
		if (redisUtils.exists(key)) {
			redisUtils.incrBy(key, 1L);
		} else {
			redisUtils.set(key, "1", 3600L);
		}
	}

	/**
	 * 清除缓存该IP地址用户登录错误次数
	 *
	 * @param ip
	 */
	public void deleteLoginErrorTimes(String ip) {
		String key = Constants.LOGIN_FAIL_IP + ip;
		if (redisUtils.exists(key)) {
			redisUtils.delete(key);
		}
	}

	/**
	 * 检测该IP地址用户登录错误次数是否超过限制
	 * 默认错误次数为5次
	 *
	 * @return
	 */
	public boolean checkLoginError(String ip) {
		boolean b = false;
		String key = Constants.LOGIN_FAIL_IP + ip;
		long max = 5L;
		if (redisUtils.exists(key) && Long.parseLong(redisUtils.get(key)) >= max) {
			b = true;
		}
		return b;
	}
}
